package cn.sxw.ld.module.dish.service.impl;

import cn.sxw.ld.module.category.service.CategoryService;
import cn.sxw.ld.module.dish.dto.DishDto;
import cn.sxw.ld.common.exception.CustomException;
import cn.sxw.ld.module.dish.mapper.DishMapper;
import cn.sxw.ld.module.category.pojo.Category;
import cn.sxw.ld.module.dish.pojo.Dish;
import cn.sxw.ld.module.dish.pojo.DishFlavor;
import cn.sxw.ld.module.dish.service.DishFlavorService;
import cn.sxw.ld.module.dish.service.DishService;
import cn.sxw.ld.module.setmeal.service.SetmealDishService;
import cn.sxw.ld.module.setmeal.service.SetmealService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * ClassName:DishServiceImpl
 * Package:cn.sjxy.graduation.service.impl
 * Description:
 *
 * @Date:2022/5/27 20:44
 * @Author:SXW
 */
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    @Autowired
    private DishFlavorService dishFlavorService;

    @Autowired
    private SetmealDishService setmealDishService;

    @Autowired
    private SetmealService setmealService;

    @Lazy
    @Autowired
    private CategoryService categoryService;

    /**
     * 思路：
     * 1. 需要查询 dish 菜品表，获得菜品数据
     * 2. 查询 dish_flavor 获得口味相关数据
     *
     * @param dishDto
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        // 保存菜品表
        this.save(dishDto);
        // 菜品口味集合
        List<DishFlavor> flavors = dishDto.getFlavors();
        /*
            口味不能单独存在，必须与 dish_id 绑定
            否则，无法准确为菜品设置口味
            做法：
                1. 先获取到集合中每一个菜品的 dish_id
                2. 将获取的 dish_id 赋值到集合
         */

        /* 从 dishDto 中取出 dish_id 的值，遍历集合中对象时，依次赋值
        // foreach 方法遍历，本方法也可用
        for (DishFlavor dishFlavor : flavors) {
            dishFlavor.setDishId(dishDto.getId());
         }

        */
        flavors = flavors.stream().map((item) -> { // item 为临时变量指向当次遍历的对象
            item.setDishId(dishDto.getId()); // 将获取的 dishId 赋值给集合中对象的 dishId
            return item;
        }).collect(Collectors.toList()); // 将操作的流转为对象，并将值赋给操作前集合

        // 存单个对象用 save，多个对象批量操作使用 saveBatch
        dishFlavorService.saveBatch(flavors);
    }

    /**
     * 查询菜品信息和口味信息
     *
     * @param id
     * @return
     */
    @Override
    public DishDto getDishAndFlavor(Long id) {
        // 从 dish 表查询当前菜品基本信息
        Dish dish = this.getById(id);

        // dishDto 对象，接收口味信息
        DishDto dishDto = new DishDto();
        BeanUtils.copyProperties(dish, dishDto); // 前为源，后为接收者

        // 创建条件构造器，查询口味信息
        LambdaQueryWrapper<DishFlavor> wrapper = new LambdaQueryWrapper<DishFlavor>();
        wrapper.eq(DishFlavor::getDishId, dish.getId()); // 根据 DishId 查询
        // 按照条件构造器查询出口味列表
        List<DishFlavor> dishFlavorList = dishFlavorService.list(wrapper);
        // 将查询到的口味列表设置到 dishDto 象中
        dishDto.setFlavors(dishFlavorList);
        return dishDto;
    }

    /**
     * 功能：修改两张表
     * 需求：修改两张表中的数据
     * 实现：
     * 1. 更新 dish 表
     * 2. 更新 DishFlavor 表
     * 3. 但是口味信息是集合，依次修改较为繁琐，考虑先清空当前列数据将传来的数据新增进去
     *
     * @param dishDto
     */
    @Override
    @Transactional
    public void updateDishAndFlavor(DishDto dishDto) {
        /* 本类中使用的 this 指泛型限定对应的 mapper */
        // 1. 更新 dish 表
        this.updateById(dishDto);

        // 2. 清理口味表中对应行的数据，delete 操作
        LambdaQueryWrapper<DishFlavor> wrapper = new LambdaQueryWrapper<>();
        // 根据 dishId 操作
        wrapper.eq(DishFlavor::getDishId, dishDto.getId());

        dishFlavorService.remove(wrapper);

        // 3. 添加传来的数据
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> { // item 为临时变量指向当次遍历的对象
            item.setDishId(dishDto.getId()); // 将获取的 dishId 赋值给集合中对象的 dishId
            return item;
        }).collect(Collectors.toList()); // 将操作的流转为对象，并将值赋给操作前集合

        // 存单个对象用 save，多个对象批量操作使用 saveBatch
        dishFlavorService.saveBatch(flavors);
    }

    @Override
    @Transactional
    public void deleteWithFlavor(List<Long> ids) {
        LambdaQueryWrapper<Dish> dishWrapper = new LambdaQueryWrapper<>();
        dishWrapper.in(Dish::getId, ids);
        dishWrapper.eq(Dish::getStatus, 1);
        Long count = this.count(dishWrapper);
        if (count > 0) {
            throw new CustomException("该菜品售卖中，删除失败！");
        }
        this.removeByIds(ids);

        // 删除口味表中数据
        LambdaQueryWrapper<DishFlavor> dishFlavorWrapper = new LambdaQueryWrapper<>();
        dishFlavorWrapper.in(DishFlavor::getDishId, ids);
        dishFlavorService.remove(dishFlavorWrapper);

    }

    /**
     * 功能：改变售卖状态
     * <p>
     * 需求：
     * 1. 根据传回的 id 查找对应的 dish
     * 2. 将数据库中的 status 的值到，设置为传回的 status
     * <p>
     * 注意：
     * 使用 @RequestParam 注解接收返回的 ids 集合
     * 接收 status 返回的状态作为启用或禁用的标记
     *
     * @param ids
     * @param status
     * @return
     */
    @Override
    public void changeStatus(List<Long> ids, Integer status) {
        // 根据传回的值，查找对应的 dish
        List<Dish> dishes = this.listByIds(ids);

        // 使用流方式读取集合中数据并修改
        dishes.stream().map((item) -> { // 一个 item 即表示一个 dish
            item.setStatus(status); // 将查询到的数据做修改
            this.updateById(item); // 将修改数据存回数据库
            return item;
        }).collect(Collectors.toList());
    }

    /**
     * <p>
     * 需求：从数据库中查询菜品信息，将其映射到对应的数据对象，发送前端展示
     * 难点：页面的展示信息没有与之一一对应的实体类，无法完整封装
     * 解决方案：创建 对应的 dto 实体类，负责数据转换映射
     * dish 表只对应的分类 id，但页面需要展示的是分类的名称
     * <p>
     * 思路：
     * 1. 查询 dish 表获取到菜品信息，也获得了菜品分类的 id
     * 2. 根据获得的分类 id 查询 category 分类表获取分类名称
     * <p>
     * 具体实现：
     * 1. 创建 dish 的分页对象，在执行分页查询后将获取菜品信息
     * 2. 创建 dishDto 的分页对象，用于接收处理好的数据
     * 3. 将 dishPage 的数据通用数据拷贝给 dishDtoPage，对存放特有数据的属性先忽略
     * 4. 处理特殊数据，遍历集合，获取 dish，并将 dish 中的属性直接赋值给 dishDto 对象
     * 5. 对于 dishDto 特有的分类名字属性则对调用分类服务，根据获取的分类 id 查询出对应的分类名
     * 6. 将分类名赋值给 dishDto对象
     * 7. 将上述 3~6 步中处理好的集合赋值给 dishDtoList
     * 8. 将 dishDtoList 赋值到 dishDto 的分页对象上
     *
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @Override
    public Page getPage(int page, int pageSize, String name) {
        // 创建分页构造器对象
        Page<Dish> dishPage = new Page<>(page, pageSize);
        // 因为 dish 中不完全包含对应信息，需要转换
        Page<DishDto> dishDtoPage = new Page<>();

        // 创建条件构造器
        LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper();
        wrapper.like(name != null, Dish::getName, name);
        wrapper.orderByDesc(Dish::getUpdateTime);

        // 执行分页查询
        this.page(dishPage, wrapper);
        // 在执行完分页查询后，对应的值就已经存储在对象中了

        // 对象拷贝，将装有值的对象，拷贝到符合页面要求的对象模型上，忽略指定的属性
        BeanUtils.copyProperties(dishPage, dishDtoPage, "records");
        // 因为 records 集合中存储的就是页面信息，所以忽略其拷贝，且做相关处理
        List<Dish> records = dishPage.getRecords();

        List<DishDto> dishDtoList = // 将处理好的集合赋值给转换对象
                records.stream().map((item) -> { // item 作为处理 records<Dish>的临时变量，存放当前遍历出的 Dish 对象
                    DishDto dishDto = new DishDto();
                    BeanUtils.copyProperties(item, dishDto); // 将item中普通属性都拷贝到dishDto中

                    // 获取分类名字
                    Long categoryId = item.getCategoryId(); // 获取分类的id
                    Category category = categoryService.getById(categoryId);// 通过分类的 id 获取一个对应的对象
                    if (category != null) { // 查询到对应信息在做设置，防止空指针异常
                        String categoryName = category.getName();// 通过该对象获取该分类的名字
                        dishDto.setCategoryName(categoryName); // 将查询到的名字赋值给对应的转换对象
                    }
                    return dishDto;
                }).collect(Collectors.toList()); // 将所有操作的对象转成集合

        dishDtoPage.setRecords(dishDtoList);

        // 最后返回的 dishDto 对象中的所有属性都是有值的
        return dishDtoPage;
    }

    /**
     * 需求：查询菜品
     * 实现：
     * 1. 查询 Dish 基本信息，复制到 dishDto 中
     * 2. 处理 dishDto 中特有数据
     * 2.1 获取分类名
     * 2.2 获取口味信息
     *
     * @param dish
     * @return
     */
    @Override
    public List<DishDto> getDishDtos(Dish dish) {
        /* 1. 获取 dish 基本信息 */
        // 创建条件构造器查询菜品基本信息
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 条件：若该菜品有归属分类，按照分类 id，查询菜品表
        dishLambdaQueryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
        // 只查询在售的，停售则不用查询出来
        dishLambdaQueryWrapper.eq(Dish::getStatus, 1);
        // 排序条件
        dishLambdaQueryWrapper.orderByDesc(Dish::getSort)
                .orderByDesc(Dish::getUpdateTime);
        // 查询
        List<Dish> dishs = this.list(dishLambdaQueryWrapper);

        List<DishDto> dishDtos = // 将处理好的集合赋值
                dishs.stream().map((item) -> { // 处理获取到的集合，集合中的对象是 Dish
                    DishDto dishDto = new DishDto();
                    BeanUtils.copyProperties(item, dishDto); // 将item中普通属性都拷贝到dishDto中

                    /* 2. 处理特有信息 */
                    // 2.1 获取分类名字
                    Long categoryId = item.getCategoryId(); // 获取分类的id
                    Category category = categoryService.getById(categoryId); // 通过分类的 id 获取一个分类对象
                    if (category != null) { // 查询到对应信息在做设置，防止空指针异常
                        String categoryName = category.getName();// 通过该对象获取该分类的名字
                        dishDto.setCategoryName(categoryName); // 将查询到的名字赋值给对应的转换对象
                    }

                    // 2.2 获取口味信息
                    Long dishId = item.getId();
                    LambdaQueryWrapper<DishFlavor> flavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
                    // 口味表中有 dish_id 字段，查询一个 dish_id 对应的所有口味信息
                    flavorLambdaQueryWrapper.eq(DishFlavor::getDishId, dishId);
                    List<DishFlavor> flavors = dishFlavorService.list(flavorLambdaQueryWrapper);
                    // 将查询到值设置给 dishDto
                    dishDto.setFlavors(flavors);

                    return dishDto;
                }).collect(Collectors.toList()); // 将所有操作的对象转成集合

        return dishDtos;
    }
}

